/*
 * Copyright 2007 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.sf.hibernate4gwt.rebind;

import java.io.PrintWriter;
import java.util.logging.Level;

import org.apache.commons.lang.StringUtils;

import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

import net.sf.hibernate4gwt.pojo.base.ILazyPojo;
import net.sf.hibernate4gwt.rebind.xml.AdditionalCode;
import net.sf.hibernate4gwt.rebind.xml.AdditionalCodeReader;
import net.sf.hibernate4gwt.rebind.xml.Attribute;
import net.sf.hibernate4gwt.rebind.xml.Method;

/**
 * Proxy generator for GWT
 * @author bruno.marchesson
 *
 */
public abstract class AbstractGwtProxyGenerator extends Generator
{
	//----
	// Attribute
	//----
	/**
	 * Associated additional code file path
	 */
	protected String _additionalCodePath;
	
	//-------------------------------------------------------------------------
	//
	// Constructor
	//
	//-------------------------------------------------------------------------
	/**
	 * Constructor
	 */
	protected AbstractGwtProxyGenerator(String filePath)
	{
		_additionalCodePath = filePath;
	}
	
	//-------------------------------------------------------------------------
	//
	// Generator interface
	//
	//-------------------------------------------------------------------------
	/* (non-Javadoc)
	 * @see com.google.gwt.core.ext.Generator#generate(com.google.gwt.core.ext.TreeLogger, com.google.gwt.core.ext.GeneratorContext, java.lang.String)
	 */
	public String generate(TreeLogger logger, 
						   GeneratorContext context,
						   String typeName) throws UnableToCompleteException
	{
		try
		{
		//	Get target class
		//
			TypeOracle typeOracle = context.getTypeOracle();
			JClassType requestedClass = typeOracle.getType(typeName);

			// Do not generate proxy for a class already implementing
			// ILazyPojo interface
			if (isLazyPojo(requestedClass) == true)
			{
				// LOGGER is not compatible from GWT 1.4 to GWT 1.5 !
//				logger.log(TreeLogger.INFO,
//						   requestedClass.getClass().getName() +
//						   " is already a lazy pojo : proxy not needed.", 
//						   null);
				System.out.println(requestedClass.getClass().getName() +
						   " is already a lazy pojo : proxy not needed.");
				return null;
			}
		    
		//	Read additional data information
		//
			AdditionalCode additionalCode = AdditionalCodeReader.readFromFile(_additionalCodePath);
		    
		//	Compute proxy information
		//
		    String packageName = requestedClass.getPackage().getName();
		    String proxyClassName = requestedClass.getSimpleSourceName() + additionalCode.getSuffix();
		    String qualifiedProxyClassName = packageName + "." + proxyClassName; 
		    String className = requestedClass.getName(); 
		    
		 // LOGGER is not compatible from GWT 1.4 to GWT 1.5 !
//		    logger.log(TreeLogger.INFO,
//					   "Generating proxy " + qualifiedProxyClassName + 
//					   " for class " + className, 
//					   null);
		    System.out.println("Generating proxy " + qualifiedProxyClassName + 
					           " for class " + className);
		    
		//	Create source writer
		//
			SourceWriter sourceWriter = getSourceWriter(logger, context, packageName, 
														proxyClassName, className, additionalCode);
			if (sourceWriter != null)
			{
				generateProxy(logger, sourceWriter, additionalCode);
				sourceWriter.commit(logger);
			
				// LOGGER is not compatible from GWT 1.4 to GWT 1.5 !
//				logger.log(TreeLogger.INFO, "Proxy generation OK", null);
				System.out.println("Proxy generation OK");
			}
			return qualifiedProxyClassName;
		}
		catch (Exception ex)
		{
			// LOGGER is not compatible from GWT 1.4 to GWT 1.5 !
//			logger.log(TreeLogger.ERROR, "Proxy generation error", ex);
			System.err.println("Proxy generation error");
			ex.printStackTrace();
			
			throw new UnableToCompleteException();
		}
	}

	//-------------------------------------------------------------------------
	//
	// Internal method
	//
	//-------------------------------------------------------------------------
	/**
	 * Create the needed source writer
	 */
	protected SourceWriter getSourceWriter(TreeLogger logger,
										   GeneratorContext context,
										   String packageName,
										   String className,
										   String superclassName,
										   AdditionalCode additionalCode)
	{
		PrintWriter printWriter = context.tryCreate(logger, packageName, className);
		if (printWriter == null)
		{
		//	Could not create print writer (the new type already exists)
		//
			return null;
		}
		
		ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(packageName, className);
		
	//	Add superclass
	//
		composerFactory.setSuperclass(superclassName);
		
	//	Add implemented interface if needed
	//
		if (StringUtils.isEmpty(additionalCode.getImplementedInterface()) == false)
		{
			composerFactory.addImplementedInterface(additionalCode.getImplementedInterface());
		}
		
	//	Create source writer
	//
		return composerFactory.createSourceWriter(context, printWriter);
	}
	
	/**
	 * Add additional code to the instrumented class
	 * @param logger
	 * @param sourceWriter
	 * @param additionalCode
	 */
	protected void generateProxy(TreeLogger logger,
								 SourceWriter sourceWriter,
								 AdditionalCode additionalCode)
	{
	//	Generate attribute
	//
		if (additionalCode.getAttributes() != null)
		{
			for (Attribute attribute : additionalCode.getAttributes())
			{
				generateAttribute(sourceWriter, attribute);
				
			}
		}
		
	//	Generate methods
	//
		if (additionalCode.getMethods() != null)
		{
			for (Method method : additionalCode.getMethods())
			{
				generateMethod(sourceWriter, method);
			}
		}
	}
		
	/**
	 * Generates an additional attribute
	 * @param sourceWriter
	 * @param attribute
	 */
	protected void generateAttribute(SourceWriter sourceWriter,
				 					 Attribute attribute)
	{
	//	Javadoc comment if needed
	//
		if (StringUtils.isEmpty(attribute.getJavadoc()) == false)
		{
			sourceWriter.beginJavaDocComment();
			sourceWriter.println(attribute.getJavadoc());
			sourceWriter.endJavaDocComment();
		}
	
	//	Add attribute
	//
		sourceWriter.println(attribute.toJava5String());
	//
	}
	
	/**
	 * Generates an additional attribute
	 * @param sourceWriter
	 * @param attribute
	 */
	protected void generateMethod(SourceWriter sourceWriter,
				 				  Method method)
	{
	//	Javadoc comment if needed
	//
		if (StringUtils.isEmpty(method.getJavadoc()) == false)
		{
			sourceWriter.beginJavaDocComment();
			sourceWriter.println(method.getJavadoc());
			sourceWriter.endJavaDocComment();
		}
	
	//	Add Signature and code
	//
		sourceWriter.println(method.computeJava5Signature());
		sourceWriter.println(method.getCode());
	}
	
	/**
	 * Check if the argument class already implements ILazyPojo
	 * @param clazz
	 * @return
	 */
	protected boolean isLazyPojo(JClassType clazz)
	{
	//	Check superclass name
	//
		String superclassName = clazz.getSuperclass().getQualifiedSourceName();
		if ((net.sf.hibernate4gwt.pojo.java5.LazyPojo.class.getCanonicalName().equals(superclassName)))
		{
			return true;
		}
		
	//	Check implemented interfaces
	//
		JClassType[] interfaceList = clazz.getImplementedInterfaces();
		for (int index = 0; index < interfaceList.length; index++)
		{
			String interfaceName = interfaceList[index].getQualifiedSourceName();
			if (ILazyPojo.class.getName().equals(interfaceName))
			{
			//	ILazyPojo
			//
				return true;
			}
			
		}
		
	//	Not lazy pojo
	//
		return false;
	}
}
